1   /*
2    * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package javax.swing.tree;
27  
28  import java.beans.PropertyChangeListener;
29  import java.io.*;
30  import java.util.ArrayList;
31  import java.util.BitSet;
32  import java.util.Enumeration;
33  import java.util.EventListener;
34  import java.util.Hashtable;
35  import java.util.List;
36  import java.util.Vector;
37  import javax.swing.event.*;
38  import javax.swing.DefaultListSelectionModel;
39  
40  /**
41   * Default implementation of TreeSelectionModel.  Listeners are notified
42   * whenever
43   * the paths in the selection change, not the rows. In order
44   * to be able to track row changes you may wish to become a listener
45   * for expansion events on the tree and test for changes from there.
46   * <p>resetRowSelection is called from any of the methods that update
47   * the selected paths. If you subclass any of these methods to
48   * filter what is allowed to be selected, be sure and message
49   * <code>resetRowSelection</code> if you do not message super.
50   *
51   * <strong>Warning:</strong>
52   * Serialized objects of this class will not be compatible with
53   * future Swing releases. The current serialization support is
54   * appropriate for short term storage or RMI between applications running
55   * the same version of Swing.  As of 1.4, support for long term storage
56   * of all JavaBeans<sup><font size="-2">TM</font></sup>
57   * has been added to the <code>java.beans</code> package.
58   * Please see {@link java.beans.XMLEncoder}.
59   *
60   * @see javax.swing.JTree
61   *
62   * @author Scott Violet
63   */
64  public class DefaultTreeSelectionModel implements Cloneable, Serializable, TreeSelectionModel
65  {
66      /** Property name for selectionMode. */
67      public static final String          SELECTION_MODE_PROPERTY = "selectionMode";
68  
69      /** Used to messaged registered listeners. */
70      protected SwingPropertyChangeSupport     changeSupport;
71  
72      /** Paths that are currently selected.  Will be null if nothing is
73        * currently selected. */
74      protected TreePath[]                selection;
75  
76      /** Event listener list. */
77      protected EventListenerList   listenerList = new EventListenerList();
78  
79      /** Provides a row for a given path. */
80      transient protected RowMapper               rowMapper;
81  
82      /** Handles maintaining the list selection model. The RowMapper is used
83       * to map from a TreePath to a row, and the value is then placed here. */
84      protected DefaultListSelectionModel     listSelectionModel;
85  
86      /** Mode for the selection, will be either SINGLE_TREE_SELECTION,
87       * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION.
88       */
89      protected int                           selectionMode;
90  
91      /** Last path that was added. */
92      protected TreePath                      leadPath;
93      /** Index of the lead path in selection. */
94      protected int                           leadIndex;
95      /** Lead row. */
96      protected int                           leadRow;
97  
98      /** Used to make sure the paths are unique, will contain all the paths
99       * in <code>selection</code>.
100      */
101     private Hashtable<TreePath, Boolean>    uniquePaths;
102     private Hashtable<TreePath, Boolean>    lastPaths;
103     private TreePath[]                      tempPaths;
104 
105 
106     /**
107      * Creates a new instance of DefaultTreeSelectionModel that is
108      * empty, with a selection mode of DISCONTIGUOUS_TREE_SELECTION.
109      */
110     public DefaultTreeSelectionModel() {
111         listSelectionModel = new DefaultListSelectionModel();
112         selectionMode = DISCONTIGUOUS_TREE_SELECTION;
113         leadIndex = leadRow = -1;
114         uniquePaths = new Hashtable<TreePath, Boolean>();
115         lastPaths = new Hashtable<TreePath, Boolean>();
116         tempPaths = new TreePath[1];
117     }
118 
119     /**
120      * Sets the RowMapper instance. This instance is used to determine
121      * the row for a particular TreePath.
122      */
123     public void setRowMapper(RowMapper newMapper) {
124         rowMapper = newMapper;
125         resetRowSelection();
126     }
127 
128     /**
129      * Returns the RowMapper instance that is able to map a TreePath to a
130      * row.
131      */
132     public RowMapper getRowMapper() {
133         return rowMapper;
134     }
135 
136     /**
137      * Sets the selection model, which must be one of SINGLE_TREE_SELECTION,
138      * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION. If mode
139      * is not one of the defined value,
140      * <code>DISCONTIGUOUS_TREE_SELECTION</code> is assumed.
141      * <p>This may change the selection if the current selection is not valid
142      * for the new mode. For example, if three TreePaths are
143      * selected when the mode is changed to <code>SINGLE_TREE_SELECTION</code>,
144      * only one TreePath will remain selected. It is up to the particular
145      * implementation to decide what TreePath remains selected.
146      * <p>
147      * Setting the mode to something other than the defined types will
148      * result in the mode becoming <code>DISCONTIGUOUS_TREE_SELECTION</code>.
149      */
150     public void setSelectionMode(int mode) {
151         int            oldMode = selectionMode;
152 
153         selectionMode = mode;
154         if(selectionMode != TreeSelectionModel.SINGLE_TREE_SELECTION &&
155            selectionMode != TreeSelectionModel.CONTIGUOUS_TREE_SELECTION &&
156            selectionMode != TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
157             selectionMode = TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION;
158         if(oldMode != selectionMode && changeSupport != null)
159             changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY,
160                                              Integer.valueOf(oldMode),
161                                              Integer.valueOf(selectionMode));
162     }
163 
164     /**
165      * Returns the selection mode, one of <code>SINGLE_TREE_SELECTION</code>,
166      * <code>DISCONTIGUOUS_TREE_SELECTION</code> or
167      * <code>CONTIGUOUS_TREE_SELECTION</code>.
168      */
169     public int getSelectionMode() {
170         return selectionMode;
171     }
172 
173     /**
174       * Sets the selection to path. If this represents a change, then
175       * the TreeSelectionListeners are notified. If <code>path</code> is
176       * null, this has the same effect as invoking <code>clearSelection</code>.
177       *
178       * @param path new path to select
179       */
180     public void setSelectionPath(TreePath path) {
181         if(path == null)
182             setSelectionPaths(null);
183         else {
184             TreePath[]          newPaths = new TreePath[1];
185 
186             newPaths[0] = path;
187             setSelectionPaths(newPaths);
188         }
189     }
190 
191     /**
192      * Sets the selection. Whether the supplied paths are taken as the
193      * new selection depends upon the selection mode. If the supplied
194      * array is {@code null}, or empty, the selection is cleared. If
195      * the selection mode is {@code SINGLE_TREE_SELECTION}, only the
196      * first path in {@code pPaths} is used. If the selection
197      * mode is {@code CONTIGUOUS_TREE_SELECTION} and the supplied paths
198      * are not contiguous, then only the first path in {@code pPaths} is
199      * used. If the selection mode is
200      * {@code DISCONTIGUOUS_TREE_SELECTION}, then all paths are used.
201      * <p>
202      * All {@code null} paths in {@code pPaths} are ignored.
203      * <p>
204      * If this represents a change, all registered {@code
205      * TreeSelectionListener}s are notified.
206      * <p>
207      * The lead path is set to the last unique path.
208      * <p>
209      * The paths returned from {@code getSelectionPaths} are in the same
210      * order as those supplied to this method.
211      *
212      * @param pPaths the new selection
213      */
214     public void setSelectionPaths(TreePath[] pPaths) {
215         int            newCount, newCounter, oldCount, oldCounter;
216         TreePath[]     paths = pPaths;
217 
218         if(paths == null)
219             newCount = 0;
220         else
221             newCount = paths.length;
222         if(selection == null)
223             oldCount = 0;
224         else
225             oldCount = selection.length;
226         if((newCount + oldCount) != 0) {
227             if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION) {
228                 /* If single selection and more than one path, only allow
229                    first. */
230                 if(newCount > 1) {
231                     paths = new TreePath[1];
232                     paths[0] = pPaths[0];
233                     newCount = 1;
234                 }
235             }
236             else if(selectionMode ==
237                     TreeSelectionModel.CONTIGUOUS_TREE_SELECTION) {
238                 /* If contiguous selection and paths aren't contiguous,
239                    only select the first path item. */
240                 if(newCount > 0 && !arePathsContiguous(paths)) {
241                     paths = new TreePath[1];
242                     paths[0] = pPaths[0];
243                     newCount = 1;
244                 }
245             }
246 
247             TreePath         beginLeadPath = leadPath;
248             Vector<PathPlaceHolder> cPaths = new Vector<PathPlaceHolder>(newCount + oldCount);
249             List<TreePath> newSelectionAsList =
250                     new ArrayList<TreePath>(newCount);
251 
252             lastPaths.clear();
253             leadPath = null;
254             /* Find the paths that are new. */
255             for(newCounter = 0; newCounter < newCount; newCounter++) {
256                 TreePath path = paths[newCounter];
257                 if (path != null && lastPaths.get(path) == null) {
258                     lastPaths.put(path, Boolean.TRUE);
259                     if (uniquePaths.get(path) == null) {
260                         cPaths.addElement(new PathPlaceHolder(path, true));
261                     }
262                     leadPath = path;
263                     newSelectionAsList.add(path);
264                 }
265             }
266 
267             TreePath[] newSelection = newSelectionAsList.toArray(
268                     new TreePath[newSelectionAsList.size()]);
269 
270             /* Get the paths that were selected but no longer selected. */
271             for(oldCounter = 0; oldCounter < oldCount; oldCounter++)
272                 if(selection[oldCounter] != null &&
273                     lastPaths.get(selection[oldCounter]) == null)
274                     cPaths.addElement(new PathPlaceHolder
275                                       (selection[oldCounter], false));
276 
277             selection = newSelection;
278 
279             Hashtable<TreePath, Boolean>  tempHT = uniquePaths;
280 
281             uniquePaths = lastPaths;
282             lastPaths = tempHT;
283             lastPaths.clear();
284 
285             // No reason to do this now, but will still call it.
286             insureUniqueness();
287 
288             updateLeadIndex();
289 
290             resetRowSelection();
291             /* Notify of the change. */
292             if(cPaths.size() > 0)
293                 notifyPathChange(cPaths, beginLeadPath);
294         }
295     }
296 
297     /**
298       * Adds path to the current selection. If path is not currently
299       * in the selection the TreeSelectionListeners are notified. This has
300       * no effect if <code>path</code> is null.
301       *
302       * @param path the new path to add to the current selection
303       */
304     public void addSelectionPath(TreePath path) {
305         if(path != null) {
306             TreePath[]            toAdd = new TreePath[1];
307 
308             toAdd[0] = path;
309             addSelectionPaths(toAdd);
310         }
311     }
312 
313     /**
314       * Adds paths to the current selection. If any of the paths in
315       * paths are not currently in the selection the TreeSelectionListeners
316       * are notified. This has
317       * no effect if <code>paths</code> is null.
318       * <p>The lead path is set to the last element in <code>paths</code>.
319       * <p>If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>,
320       * and adding the new paths would make the selection discontiguous.
321       * Then two things can result: if the TreePaths in <code>paths</code>
322       * are contiguous, then the selection becomes these TreePaths,
323       * otherwise the TreePaths aren't contiguous and the selection becomes
324       * the first TreePath in <code>paths</code>.
325       *
326       * @param paths the new path to add to the current selection
327       */
328     public void addSelectionPaths(TreePath[] paths) {
329         int       newPathLength = ((paths == null) ? 0 : paths.length);
330 
331         if(newPathLength > 0) {
332             if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION) {
333                 setSelectionPaths(paths);
334             }
335             else if(selectionMode == TreeSelectionModel.
336                     CONTIGUOUS_TREE_SELECTION && !canPathsBeAdded(paths)) {
337                 if(arePathsContiguous(paths)) {
338                     setSelectionPaths(paths);
339                 }
340                 else {
341                     TreePath[]          newPaths = new TreePath[1];
342 
343                     newPaths[0] = paths[0];
344                     setSelectionPaths(newPaths);
345                 }
346             }
347             else {
348                 int               counter, validCount;
349                 int               oldCount;
350                 TreePath          beginLeadPath = leadPath;
351                 Vector<PathPlaceHolder>  cPaths = null;
352 
353                 if(selection == null)
354                     oldCount = 0;
355                 else
356                     oldCount = selection.length;
357                 /* Determine the paths that aren't currently in the
358                    selection. */
359                 lastPaths.clear();
360                 for(counter = 0, validCount = 0; counter < newPathLength;
361                     counter++) {
362                     if(paths[counter] != null) {
363                         if (uniquePaths.get(paths[counter]) == null) {
364                             validCount++;
365                             if(cPaths == null)
366                                 cPaths = new Vector<PathPlaceHolder>();
367                             cPaths.addElement(new PathPlaceHolder
368                                               (paths[counter], true));
369                             uniquePaths.put(paths[counter], Boolean.TRUE);
370                             lastPaths.put(paths[counter], Boolean.TRUE);
371                         }
372                         leadPath = paths[counter];
373                     }
374                 }
375 
376                 if(leadPath == null) {
377                     leadPath = beginLeadPath;
378                 }
379 
380                 if(validCount > 0) {
381                     TreePath         newSelection[] = new TreePath[oldCount +
382                                                                   validCount];
383 
384                     /* And build the new selection. */
385                     if(oldCount > 0)
386                         System.arraycopy(selection, 0, newSelection, 0,
387                                          oldCount);
388                     if(validCount != paths.length) {
389                         /* Some of the paths in paths are already in
390                            the selection. */
391                         Enumeration<TreePath> newPaths = lastPaths.keys();
392 
393                         counter = oldCount;
394                         while (newPaths.hasMoreElements()) {
395                             newSelection[counter++] = newPaths.nextElement();
396                         }
397                     }
398                     else {
399                         System.arraycopy(paths, 0, newSelection, oldCount,
400                                          validCount);
401                     }
402 
403                     selection = newSelection;
404 
405                     insureUniqueness();
406 
407                     updateLeadIndex();
408 
409                     resetRowSelection();
410 
411                     notifyPathChange(cPaths, beginLeadPath);
412                 }
413                 else
414                     leadPath = beginLeadPath;
415                 lastPaths.clear();
416             }
417         }
418     }
419 
420     /**
421       * Removes path from the selection. If path is in the selection
422       * The TreeSelectionListeners are notified. This has no effect if
423       * <code>path</code> is null.
424       *
425       * @param path the path to remove from the selection
426       */
427     public void removeSelectionPath(TreePath path) {
428         if(path != null) {
429             TreePath[]             rPath = new TreePath[1];
430 
431             rPath[0] = path;
432             removeSelectionPaths(rPath);
433         }
434     }
435 
436     /**
437       * Removes paths from the selection.  If any of the paths in paths
438       * are in the selection the TreeSelectionListeners are notified.
439       * This has no effect if <code>paths</code> is null.
440       *
441       * @param paths the paths to remove from the selection
442       */
443     public void removeSelectionPaths(TreePath[] paths) {
444         if (paths != null && selection != null && paths.length > 0) {
445             if(!canPathsBeRemoved(paths)) {
446                 /* Could probably do something more interesting here! */
447                 clearSelection();
448             }
449             else {
450                 Vector<PathPlaceHolder> pathsToRemove = null;
451 
452                 /* Find the paths that can be removed. */
453                 for (int removeCounter = paths.length - 1; removeCounter >= 0;
454                      removeCounter--) {
455                     if(paths[removeCounter] != null) {
456                         if (uniquePaths.get(paths[removeCounter]) != null) {
457                             if(pathsToRemove == null)
458                                 pathsToRemove = new Vector<PathPlaceHolder>(paths.length);
459                             uniquePaths.remove(paths[removeCounter]);
460                             pathsToRemove.addElement(new PathPlaceHolder
461                                          (paths[removeCounter], false));
462                         }
463                     }
464                 }
465                 if(pathsToRemove != null) {
466                     int         removeCount = pathsToRemove.size();
467                     TreePath    beginLeadPath = leadPath;
468 
469                     if(removeCount == selection.length) {
470                         selection = null;
471                     }
472                     else {
473                         Enumeration<TreePath> pEnum = uniquePaths.keys();
474                         int                  validCount = 0;
475 
476                         selection = new TreePath[selection.length -
477                                                 removeCount];
478                         while (pEnum.hasMoreElements()) {
479                             selection[validCount++] = pEnum.nextElement();
480                         }
481                     }
482                     if (leadPath != null &&
483                         uniquePaths.get(leadPath) == null) {
484                         if (selection != null) {
485                             leadPath = selection[selection.length - 1];
486                         }
487                         else {
488                             leadPath = null;
489                         }
490                     }
491                     else if (selection != null) {
492                         leadPath = selection[selection.length - 1];
493                     }
494                     else {
495                         leadPath = null;
496                     }
497                     updateLeadIndex();
498 
499                     resetRowSelection();
500 
501                     notifyPathChange(pathsToRemove, beginLeadPath);
502                 }
503             }
504         }
505     }
506 
507     /**
508       * Returns the first path in the selection. This is useful if there
509       * if only one item currently selected.
510       */
511     public TreePath getSelectionPath() {
512         if (selection != null && selection.length > 0) {
513             return selection[0];
514         }
515         return null;
516     }
517 
518     /**
519       * Returns the selection.
520       *
521       * @return the selection
522       */
523     public TreePath[] getSelectionPaths() {
524         if(selection != null) {
525             int                 pathSize = selection.length;
526             TreePath[]          result = new TreePath[pathSize];
527 
528             System.arraycopy(selection, 0, result, 0, pathSize);
529             return result;
530         }
531         return new TreePath[0];
532     }
533 
534     /**
535      * Returns the number of paths that are selected.
536      */
537     public int getSelectionCount() {
538         return (selection == null) ? 0 : selection.length;
539     }
540 
541     /**
542       * Returns true if the path, <code>path</code>,
543       * is in the current selection.
544       */
545     public boolean isPathSelected(TreePath path) {
546         return (path != null) ? (uniquePaths.get(path) != null) : false;
547     }
548 
549     /**
550       * Returns true if the selection is currently empty.
551       */
552     public boolean isSelectionEmpty() {
553         return (selection == null || selection.length == 0);
554     }
555 
556     /**
557       * Empties the current selection.  If this represents a change in the
558       * current selection, the selection listeners are notified.
559       */
560     public void clearSelection() {
561         if (selection != null && selection.length > 0) {
562             int                    selSize = selection.length;
563             boolean[]              newness = new boolean[selSize];
564 
565             for(int counter = 0; counter < selSize; counter++)
566                 newness[counter] = false;
567 
568             TreeSelectionEvent     event = new TreeSelectionEvent
569                 (this, selection, newness, leadPath, null);
570 
571             leadPath = null;
572             leadIndex = leadRow = -1;
573             uniquePaths.clear();
574             selection = null;
575             resetRowSelection();
576             fireValueChanged(event);
577         }
578     }
579 
580     /**
581       * Adds x to the list of listeners that are notified each time the
582       * set of selected TreePaths changes.
583       *
584       * @param x the new listener to be added
585       */
586     public void addTreeSelectionListener(TreeSelectionListener x) {
587         listenerList.add(TreeSelectionListener.class, x);
588     }
589 
590     /**
591       * Removes x from the list of listeners that are notified each time
592       * the set of selected TreePaths changes.
593       *
594       * @param x the listener to remove
595       */
596     public void removeTreeSelectionListener(TreeSelectionListener x) {
597         listenerList.remove(TreeSelectionListener.class, x);
598     }
599 
600     /**
601      * Returns an array of all the tree selection listeners
602      * registered on this model.
603      *
604      * @return all of this model's <code>TreeSelectionListener</code>s
605      *         or an empty
606      *         array if no tree selection listeners are currently registered
607      *
608      * @see #addTreeSelectionListener
609      * @see #removeTreeSelectionListener
610      *
611      * @since 1.4
612      */
613     public TreeSelectionListener[] getTreeSelectionListeners() {
614         return listenerList.getListeners(TreeSelectionListener.class);
615     }
616 
617     /**
618      * Notifies all listeners that are registered for
619      * tree selection events on this object.
620      * @see #addTreeSelectionListener
621      * @see EventListenerList
622      */
623     protected void fireValueChanged(TreeSelectionEvent e) {
624         // Guaranteed to return a non-null array
625         Object[] listeners = listenerList.getListenerList();
626         // TreeSelectionEvent e = null;
627         // Process the listeners last to first, notifying
628         // those that are interested in this event
629         for (int i = listeners.length-2; i>=0; i-=2) {
630             if (listeners[i]==TreeSelectionListener.class) {
631                 // Lazily create the event:
632                 // if (e == null)
633                 // e = new ListSelectionEvent(this, firstIndex, lastIndex);
634                 ((TreeSelectionListener)listeners[i+1]).valueChanged(e);
635             }
636         }
637     }
638 
639     /**
640      * Returns an array of all the objects currently registered
641      * as <code><em>Foo</em>Listener</code>s
642      * upon this model.
643      * <code><em>Foo</em>Listener</code>s are registered using the
644      * <code>add<em>Foo</em>Listener</code> method.
645      *
646      * <p>
647      *
648      * You can specify the <code>listenerType</code> argument
649      * with a class literal,
650      * such as
651      * <code><em>Foo</em>Listener.class</code>.
652      * For example, you can query a
653      * <code>DefaultTreeSelectionModel</code> <code>m</code>
654      * for its tree selection listeners with the following code:
655      *
656      * <pre>TreeSelectionListener[] tsls = (TreeSelectionListener[])(m.getListeners(TreeSelectionListener.class));</pre>
657      *
658      * If no such listeners exist, this method returns an empty array.
659      *
660      * @param listenerType the type of listeners requested; this parameter
661      *          should specify an interface that descends from
662      *          <code>java.util.EventListener</code>
663      * @return an array of all objects registered as
664      *          <code><em>Foo</em>Listener</code>s on this component,
665      *          or an empty array if no such
666      *          listeners have been added
667      * @exception ClassCastException if <code>listenerType</code>
668      *          doesn't specify a class or interface that implements
669      *          <code>java.util.EventListener</code>
670      *
671      * @see #getTreeSelectionListeners
672      * @see #getPropertyChangeListeners
673      *
674      * @since 1.3
675      */
676     public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
677         return listenerList.getListeners(listenerType);
678     }
679 
680     /**
681      * Returns the selection in terms of rows. There is not
682      * necessarily a one-to-one mapping between the {@code TreePath}s
683      * returned from {@code getSelectionPaths} and this method. In
684      * particular, if a {@code TreePath} is not viewable (the {@code
685      * RowMapper} returns {@code -1} for the row corresponding to the
686      * {@code TreePath}), then the corresponding row is not included
687      * in the returned array. For example, if the selection consists
688      * of two paths, {@code A} and {@code B}, with {@code A} at row
689      * {@code 10}, and {@code B} not currently viewable, then this method
690      * returns an array with the single entry {@code 10}.
691      *
692      * @return the selection in terms of rows
693      */
694     public int[] getSelectionRows() {
695         // This is currently rather expensive.  Needs
696         // to be better support from ListSelectionModel to speed this up.
697         if (rowMapper != null && selection != null && selection.length > 0) {
698             int[]      rows = rowMapper.getRowsForPaths(selection);
699 
700             if (rows != null) {
701                 int       invisCount = 0;
702 
703                 for (int counter = rows.length - 1; counter >= 0; counter--) {
704                     if (rows[counter] == -1) {
705                         invisCount++;
706                     }
707                 }
708                 if (invisCount > 0) {
709                     if (invisCount == rows.length) {
710                         rows = null;
711                     }
712                     else {
713                         int[]    tempRows = new int[rows.length - invisCount];
714 
715                         for (int counter = rows.length - 1, visCounter = 0;
716                              counter >= 0; counter--) {
717                             if (rows[counter] != -1) {
718                                 tempRows[visCounter++] = rows[counter];
719                             }
720                         }
721                         rows = tempRows;
722                     }
723                 }
724             }
725             return rows;
726         }
727         return new int[0];
728     }
729 
730     /**
731      * Returns the smallest value obtained from the RowMapper for the
732      * current set of selected TreePaths. If nothing is selected,
733      * or there is no RowMapper, this will return -1.
734       */
735     public int getMinSelectionRow() {
736         return listSelectionModel.getMinSelectionIndex();
737     }
738 
739     /**
740      * Returns the largest value obtained from the RowMapper for the
741      * current set of selected TreePaths. If nothing is selected,
742      * or there is no RowMapper, this will return -1.
743       */
744     public int getMaxSelectionRow() {
745         return listSelectionModel.getMaxSelectionIndex();
746     }
747 
748     /**
749       * Returns true if the row identified by <code>row</code> is selected.
750       */
751     public boolean isRowSelected(int row) {
752         return listSelectionModel.isSelectedIndex(row);
753     }
754 
755     /**
756      * Updates this object's mapping from TreePath to rows. This should
757      * be invoked when the mapping from TreePaths to integers has changed
758      * (for example, a node has been expanded).
759      * <p>You do not normally have to call this, JTree and its associated
760      * Listeners will invoke this for you. If you are implementing your own
761      * View class, then you will have to invoke this.
762      * <p>This will invoke <code>insureRowContinuity</code> to make sure
763      * the currently selected TreePaths are still valid based on the
764      * selection mode.
765      */
766     public void resetRowSelection() {
767         listSelectionModel.clearSelection();
768         if(selection != null && rowMapper != null) {
769             int               aRow;
770             int               validCount = 0;
771             int[]             rows = rowMapper.getRowsForPaths(selection);
772 
773             for(int counter = 0, maxCounter = selection.length;
774                 counter < maxCounter; counter++) {
775                 aRow = rows[counter];
776                 if(aRow != -1) {
777                     listSelectionModel.addSelectionInterval(aRow, aRow);
778                 }
779             }
780             if(leadIndex != -1 && rows != null) {
781                 leadRow = rows[leadIndex];
782             }
783             else if (leadPath != null) {
784                 // Lead selection path doesn't have to be in the selection.
785                 tempPaths[0] = leadPath;
786                 rows = rowMapper.getRowsForPaths(tempPaths);
787                 leadRow = (rows != null) ? rows[0] : -1;
788             }
789             else {
790                 leadRow = -1;
791             }
792             insureRowContinuity();
793 
794         }
795         else
796             leadRow = -1;
797     }
798 
799     /**
800      * Returns the lead selection index. That is the last index that was
801      * added.
802      */
803     public int getLeadSelectionRow() {
804         return leadRow;
805     }
806 
807     /**
808      * Returns the last path that was added. This may differ from the
809      * leadSelectionPath property maintained by the JTree.
810      */
811     public TreePath getLeadSelectionPath() {
812         return leadPath;
813     }
814 
815     /**
816      * Adds a PropertyChangeListener to the listener list.
817      * The listener is registered for all properties.
818      * <p>
819      * A PropertyChangeEvent will get fired when the selection mode
820      * changes.
821      *
822      * @param listener  the PropertyChangeListener to be added
823      */
824     public synchronized void addPropertyChangeListener(
825                                 PropertyChangeListener listener) {
826         if (changeSupport == null) {
827             changeSupport = new SwingPropertyChangeSupport(this);
828         }
829         changeSupport.addPropertyChangeListener(listener);
830     }
831 
832     /**
833      * Removes a PropertyChangeListener from the listener list.
834      * This removes a PropertyChangeListener that was registered
835      * for all properties.
836      *
837      * @param listener  the PropertyChangeListener to be removed
838      */
839 
840     public synchronized void removePropertyChangeListener(
841                                 PropertyChangeListener listener) {
842         if (changeSupport == null) {
843             return;
844         }
845         changeSupport.removePropertyChangeListener(listener);
846     }
847 
848     /**
849      * Returns an array of all the property change listeners
850      * registered on this <code>DefaultTreeSelectionModel</code>.
851      *
852      * @return all of this model's <code>PropertyChangeListener</code>s
853      *         or an empty
854      *         array if no property change listeners are currently registered
855      *
856      * @see #addPropertyChangeListener
857      * @see #removePropertyChangeListener
858      *
859      * @since 1.4
860      */
861     public PropertyChangeListener[] getPropertyChangeListeners() {
862         if (changeSupport == null) {
863             return new PropertyChangeListener[0];
864         }
865         return changeSupport.getPropertyChangeListeners();
866     }
867 
868     /**
869      * Makes sure the currently selected <code>TreePath</code>s are valid
870      * for the current selection mode.
871      * If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>
872      * and a <code>RowMapper</code> exists, this will make sure all
873      * the rows are contiguous, that is, when sorted all the rows are
874      * in order with no gaps.
875      * If the selection isn't contiguous, the selection is
876      * reset to contain the first set, when sorted, of contiguous rows.
877      * <p>
878      * If the selection mode is <code>SINGLE_TREE_SELECTION</code> and
879      * more than one TreePath is selected, the selection is reset to
880      * contain the first path currently selected.
881      */
882     protected void insureRowContinuity() {
883         if(selectionMode == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION &&
884            selection != null && rowMapper != null) {
885             DefaultListSelectionModel lModel = listSelectionModel;
886             int                       min = lModel.getMinSelectionIndex();
887 
888             if(min != -1) {
889                 for(int counter = min,
890                         maxCounter = lModel.getMaxSelectionIndex();
891                         counter <= maxCounter; counter++) {
892                     if(!lModel.isSelectedIndex(counter)) {
893                         if(counter == min) {
894                             clearSelection();
895                         }
896                         else {
897                             TreePath[] newSel = new TreePath[counter - min];
898                             int selectionIndex[] = rowMapper.getRowsForPaths(selection);
899                             // find the actual selection pathes corresponded to the
900                             // rows of the new selection
901                             for (int i = 0; i < selectionIndex.length; i++) {
902                                 if (selectionIndex[i]<counter) {
903                                     newSel[selectionIndex[i]-min] = selection[i];
904                                 }
905                             }
906                             setSelectionPaths(newSel);
907                             break;
908                         }
909                     }
910                 }
911             }
912         }
913         else if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION &&
914                 selection != null && selection.length > 1) {
915             setSelectionPath(selection[0]);
916         }
917     }
918 
919     /**
920      * Returns true if the paths are contiguous,
921      * or this object has no RowMapper.
922      */
923     protected boolean arePathsContiguous(TreePath[] paths) {
924         if(rowMapper == null || paths.length < 2)
925             return true;
926         else {
927             BitSet                             bitSet = new BitSet(32);
928             int                                anIndex, counter, min;
929             int                                pathCount = paths.length;
930             int                                validCount = 0;
931             TreePath[]                         tempPath = new TreePath[1];
932 
933             tempPath[0] = paths[0];
934             min = rowMapper.getRowsForPaths(tempPath)[0];
935             for(counter = 0; counter < pathCount; counter++) {
936                 if(paths[counter] != null) {
937                     tempPath[0] = paths[counter];
938                     int[] rows = rowMapper.getRowsForPaths(tempPath);
939                     if (rows == null) {
940                         return false;
941                     }
942                     anIndex = rows[0];
943                     if(anIndex == -1 || anIndex < (min - pathCount) ||
944                        anIndex > (min + pathCount))
945                         return false;
946                     if(anIndex < min)
947                         min = anIndex;
948                     if(!bitSet.get(anIndex)) {
949                         bitSet.set(anIndex);
950                         validCount++;
951                     }
952                 }
953             }
954             int          maxCounter = validCount + min;
955 
956             for(counter = min; counter < maxCounter; counter++)
957                 if(!bitSet.get(counter))
958                     return false;
959         }
960         return true;
961     }
962 
963     /**
964      * Used to test if a particular set of <code>TreePath</code>s can
965      * be added. This will return true if <code>paths</code> is null (or
966      * empty), or this object has no RowMapper, or nothing is currently selected,
967      * or the selection mode is <code>DISCONTIGUOUS_TREE_SELECTION</code>, or
968      * adding the paths to the current selection still results in a
969      * contiguous set of <code>TreePath</code>s.
970      */
971     protected boolean canPathsBeAdded(TreePath[] paths) {
972         if(paths == null || paths.length == 0 || rowMapper == null ||
973            selection == null || selectionMode ==
974            TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
975             return true;
976         else {
977             BitSet                       bitSet = new BitSet();
978             DefaultListSelectionModel    lModel = listSelectionModel;
979             int                          anIndex;
980             int                          counter;
981             int                          min = lModel.getMinSelectionIndex();
982             int                          max = lModel.getMaxSelectionIndex();
983             TreePath[]                   tempPath = new TreePath[1];
984 
985             if(min != -1) {
986                 for(counter = min; counter <= max; counter++) {
987                     if(lModel.isSelectedIndex(counter))
988                         bitSet.set(counter);
989                 }
990             }
991             else {
992                 tempPath[0] = paths[0];
993                 min = max = rowMapper.getRowsForPaths(tempPath)[0];
994             }
995             for(counter = paths.length - 1; counter >= 0; counter--) {
996                 if(paths[counter] != null) {
997                     tempPath[0] = paths[counter];
998                     int[]   rows = rowMapper.getRowsForPaths(tempPath);
999                     if (rows == null) {
1000                         return false;
1001                     }
1002                     anIndex = rows[0];
1003                     min = Math.min(anIndex, min);
1004                     max = Math.max(anIndex, max);
1005                     if(anIndex == -1)
1006                         return false;
1007                     bitSet.set(anIndex);
1008                 }
1009             }
1010             for(counter = min; counter <= max; counter++)
1011                 if(!bitSet.get(counter))
1012                     return false;
1013         }
1014         return true;
1015     }
1016 
1017     /**
1018      * Returns true if the paths can be removed without breaking the
1019      * continuity of the model.
1020      * This is rather expensive.
1021      */
1022     protected boolean canPathsBeRemoved(TreePath[] paths) {
1023         if(rowMapper == null || selection == null ||
1024            selectionMode == TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
1025             return true;
1026         else {
1027             BitSet               bitSet = new BitSet();
1028             int                  counter;
1029             int                  pathCount = paths.length;
1030             int                  anIndex;
1031             int                  min = -1;
1032             int                  validCount = 0;
1033             TreePath[]           tempPath = new TreePath[1];
1034             int[]                rows;
1035 
1036             /* Determine the rows for the removed entries. */
1037             lastPaths.clear();
1038             for (counter = 0; counter < pathCount; counter++) {
1039                 if (paths[counter] != null) {
1040                     lastPaths.put(paths[counter], Boolean.TRUE);
1041                 }
1042             }
1043             for(counter = selection.length - 1; counter >= 0; counter--) {
1044                 if(lastPaths.get(selection[counter]) == null) {
1045                     tempPath[0] = selection[counter];
1046                     rows = rowMapper.getRowsForPaths(tempPath);
1047                     if(rows != null && rows[0] != -1 && !bitSet.get(rows[0])) {
1048                         validCount++;
1049                         if(min == -1)
1050                             min = rows[0];
1051                         else
1052                             min = Math.min(min, rows[0]);
1053                         bitSet.set(rows[0]);
1054                     }
1055                 }
1056             }
1057             lastPaths.clear();
1058             /* Make sure they are contiguous. */
1059             if(validCount > 1) {
1060                 for(counter = min + validCount - 1; counter >= min;
1061                     counter--)
1062                     if(!bitSet.get(counter))
1063                         return false;
1064             }
1065         }
1066         return true;
1067     }
1068 
1069     /**
1070      * Notifies listeners of a change in path. changePaths should contain
1071      * instances of PathPlaceHolder.
1072      *
1073      * @deprecated As of JDK version 1.7
1074      */
1075     @Deprecated
1076     protected void notifyPathChange(Vector changedPaths,
1077                                     TreePath oldLeadSelection) {
1078         int                    cPathCount = changedPaths.size();
1079         boolean[]              newness = new boolean[cPathCount];
1080         TreePath[]            paths = new TreePath[cPathCount];
1081         PathPlaceHolder        placeholder;
1082 
1083         for(int counter = 0; counter < cPathCount; counter++) {
1084             placeholder = (PathPlaceHolder) changedPaths.elementAt(counter);
1085             newness[counter] = placeholder.isNew;
1086             paths[counter] = placeholder.path;
1087         }
1088 
1089         TreeSelectionEvent     event = new TreeSelectionEvent
1090                           (this, paths, newness, oldLeadSelection, leadPath);
1091 
1092         fireValueChanged(event);
1093     }
1094 
1095     /**
1096      * Updates the leadIndex instance variable.
1097      */
1098     protected void updateLeadIndex() {
1099         if(leadPath != null) {
1100             if(selection == null) {
1101                 leadPath = null;
1102                 leadIndex = leadRow = -1;
1103             }
1104             else {
1105                 leadRow = leadIndex = -1;
1106                 for(int counter = selection.length - 1; counter >= 0;
1107                     counter--) {
1108                     // Can use == here since we know leadPath came from
1109                     // selection
1110                     if(selection[counter] == leadPath) {
1111                         leadIndex = counter;
1112                         break;
1113                     }
1114                 }
1115             }
1116         }
1117         else {
1118             leadIndex = -1;
1119         }
1120     }
1121 
1122     /**
1123      * This method is obsolete and its implementation is now a noop.  It's
1124      * still called by setSelectionPaths and addSelectionPaths, but only
1125      * for backwards compatability.
1126      */
1127     protected void insureUniqueness() {
1128     }
1129 
1130 
1131     /**
1132      * Returns a string that displays and identifies this
1133      * object's properties.
1134      *
1135      * @return a String representation of this object
1136      */
1137     public String toString() {
1138         int                selCount = getSelectionCount();
1139         StringBuffer       retBuffer = new StringBuffer();
1140         int[]              rows;
1141 
1142         if(rowMapper != null)
1143             rows = rowMapper.getRowsForPaths(selection);
1144         else
1145             rows = null;
1146         retBuffer.append(getClass().getName() + " " + hashCode() + " [ ");
1147         for(int counter = 0; counter < selCount; counter++) {
1148             if(rows != null)
1149                 retBuffer.append(selection[counter].toString() + "@" +
1150                                  Integer.toString(rows[counter])+ " ");
1151             else
1152                 retBuffer.append(selection[counter].toString() + " ");
1153         }
1154         retBuffer.append("]");
1155         return retBuffer.toString();
1156     }
1157 
1158     /**
1159      * Returns a clone of this object with the same selection.
1160      * This method does not duplicate
1161      * selection listeners and property listeners.
1162      *
1163      * @exception CloneNotSupportedException never thrown by instances of
1164      *                                       this class
1165      */
1166     public Object clone() throws CloneNotSupportedException {
1167         DefaultTreeSelectionModel        clone = (DefaultTreeSelectionModel)
1168                             super.clone();
1169 
1170         clone.changeSupport = null;
1171         if(selection != null) {
1172             int              selLength = selection.length;
1173 
1174             clone.selection = new TreePath[selLength];
1175             System.arraycopy(selection, 0, clone.selection, 0, selLength);
1176         }
1177         clone.listenerList = new EventListenerList();
1178         clone.listSelectionModel = (DefaultListSelectionModel)
1179             listSelectionModel.clone();
1180         clone.uniquePaths = new Hashtable<TreePath, Boolean>();
1181         clone.lastPaths = new Hashtable<TreePath, Boolean>();
1182         clone.tempPaths = new TreePath[1];
1183         return clone;
1184     }
1185 
1186     // Serialization support.
1187     private void writeObject(ObjectOutputStream s) throws IOException {
1188         Object[]             tValues;
1189 
1190         s.defaultWriteObject();
1191         // Save the rowMapper, if it implements Serializable
1192         if(rowMapper != null && rowMapper instanceof Serializable) {
1193             tValues = new Object[2];
1194             tValues[0] = "rowMapper";
1195             tValues[1] = rowMapper;
1196         }
1197         else
1198             tValues = new Object[0];
1199         s.writeObject(tValues);
1200     }
1201 
1202 
1203     private void readObject(ObjectInputStream s)
1204         throws IOException, ClassNotFoundException {
1205         Object[]      tValues;
1206 
1207         s.defaultReadObject();
1208 
1209         tValues = (Object[])s.readObject();
1210 
1211         if(tValues.length > 0 && tValues[0].equals("rowMapper"))
1212             rowMapper = (RowMapper)tValues[1];
1213     }
1214 }
1215 
1216 /**
1217  * Holds a path and whether or not it is new.
1218  */
1219 class PathPlaceHolder {
1220     protected boolean             isNew;
1221     protected TreePath           path;
1222 
1223     PathPlaceHolder(TreePath path, boolean isNew) {
1224         this.path = path;
1225         this.isNew = isNew;
1226     }
1227 }